#include"huffman.h"
//函数compress()，读取文件内容并加以压缩，将压缩内容写入另一个文档
int compress(const char* filename, const char* outputfile)
{
    char temparray[256];
    unsigned char c;
    long i, j, m, n, f;
    long lowest, pt, filelength;
    FILE* inputfilep, * outputfilep;
    int k, q;
    inputfilep = fopen(filename, "rb");//打开文件夹的原始文件
    if (inputfilep == NULL)
    {
        printf("打开文件失败\n");
        return 0;
    }
    outputfilep = fopen(outputfile, "wb");//打开压缩后存储信息的文件，如果没有就创建
    if (outputfilep == NULL)
    {
        printf("打开文件失败\n");
        return 0;
    }
    filelength = 0;
    while (!feof(inputfilep))//功能是检测流上的文件结束符，如果文件结束，则返回非0值，否则返回0
    {
        fread(&c, 1, 1, inputfilep);//从给定文件inputfilep 读取数据到c
        TreeNode[c].count++;//读文件，统计每个字符出现次数，并且使用是ASCII码
        filelength++;//记录文件的字符总数
    }
    filelength--;//去掉结束符
    TreeNode[c].count--;
    for (i = 0; i < 512; i++) //huffman算法中初始节点的设置
    {
        if (TreeNode[i].count != 0)//512个不一定都用了
            TreeNode[i].letter = i;//映射成ASCII码
        else//赋值-1是为了避免哈夫曼编码时使用0出现冲突
            TreeNode[i].letter = -1;
        TreeNode[i].parent = -1;
        TreeNode[i].lchild = TreeNode[i].rchild = -1;
    }
    //将节点按出现次数排序（选择排序）
    for (i = 0; i < 256; i++)//每次都向后完成一次
    {
        k = i;//k用来记录变化的位置
        for (q = i + 1; q < 256; q++)//每次都在没有排序的数里进行循环
            if (TreeNode[k].count < TreeNode[q].count)
                k = q;//挑没排序的数里数据最大的
        temp = TreeNode[i];//用t来暂时存储数据（为了与后面的小数据交换）
        TreeNode[i] = TreeNode[k];//把数据最大的赋值
        TreeNode[k] = temp;//把暂存的数据赋值给原来值大的，实现交换
    }
    for (i = 0; i < 256; i++)//统计不同字符的数量
    {
        if (TreeNode[i].count == 0)
            break;
    }//统计出i的数值，以便下面使用
    n = i;
    m = 2 * n - 1;//m是总结点数
    for (i = n; i < m; i++)//使用空结点
    {
        lowest = 999999999;//保证后面的if一定开始执行
        for (j = 0; j < i; j++)
        {
            if (TreeNode[j].parent != -1) 
                continue;
            if (lowest > TreeNode[j].count)
            {
                lowest = TreeNode[j].count;
                pt = j;
            }
        }
        TreeNode[i].count = TreeNode[pt].count;
        TreeNode[pt].parent = i;
        TreeNode[i].lchild = pt;
        lowest = 999999999;
        for (j = 0; j < i; j++)
        {
            if (TreeNode[j].parent != -1) 
                continue;
            if (lowest > TreeNode[j].count)
            {
                lowest = TreeNode[j].count;
                pt = j;
            }
        }
        TreeNode[i].count += TreeNode[pt].count;
        TreeNode[i].rchild = pt;
        TreeNode[pt].parent = i;
    }
    for (i = 0; i < n; i++)//设置字符的编码
    {
        f = i;//从构造数的第一个结点开始，依次对结点进行编排
        TreeNode[i].bits[0] = 0;//初始化
        while (TreeNode[f].parent != -1)//只要有父节点，就进行编排，第一次没有父节点就是根节点
        {
            j = f;
            f = TreeNode[f].parent;
            if (TreeNode[f].lchild == j)//左孩子编0
            {
                j = strlen(TreeNode[i].bits);
                memmove(TreeNode[i].bits + 1, TreeNode[i].bits, j + 1);
                TreeNode[i].bits[0] = '0';
            }
            else//右孩子编1
            {
                j = strlen(TreeNode[i].bits);
                memmove(TreeNode[i].bits + 1, TreeNode[i].bits, j + 1);
                TreeNode[i].bits[0] = '1';
            }
        }
    }
    //下面的就是读原文件的每一个字符，按照设置好的编码替换文件中的字符
    fseek(inputfilep, 0, SEEK_SET);    //将指针定在文件起始位置，没有任何偏移
    fseek(outputfilep, 8, SEEK_SET);   //以8位二进制数为单位,每次移动偏移进行读取
    temparray[0] = 0;
    pt = 8;//后面写入了一共八字节的filelength和pt
    printf("读取将要压缩的文件:%s\n", filename);
    printf("当前文件有:%d字符\n", filelength);
    printf("二进制文件是：\n");
    //压缩文件
    while (!feof(inputfilep))
    {
        c = fgetc(inputfilep);//获取下一个字符（一个无符号字符）
        for (i = 0; i < n; i++)//n是字符种类总数
        {
            if (c == TreeNode[i].letter) //如果读取的字符与我们字符种类映射的ACSCII某一个对应
                break;
        }
        strcat(temparray, TreeNode[i].bits);//那么就把此字符种类的编码追加给temparray来暂存
        j = strlen(temparray);//j用来暂存初始编码总长
        c = 0;//二进制是00000000
        if (j >= 8)//只要存的编码位数大于8，就执行操作写入文件
        {
            for (i = 0; i < 8; i++)//按照八位二进制数转化写入文件一次进行压缩
            {
                if (temparray[i] == '1') 
                {
                    c = c << 1;
                    c = c + 1;
                }//c的二进制数左移并加1
                else 
                    c = c << 1;//c的二进制数字左移
            }//实现二进制的读取和记录
            fwrite(&c, sizeof(char), 1, outputfilep);
            printf("%s",temparray);//temparray数组存储的是char保存的二进制内容
            pt++;//pt使用的初始值是8,用于记录字节数
            strcpy(temparray, temparray + 8);//temparray的值重新覆盖
        }
    }
    if (j > 0)//当剩余字符数量少于8个时
    {
        strcat(temparray, "00000000");
        for (i = 0; i < 8; i++)
        {
            if (temparray[i] == '1')
            {
                c = c << 1;
                c += 1;//位运算
            }
            else 
                c = c << 1;//对不足的位数进行补零
        }
        fwrite(&c, 1, 1, outputfilep);
        pt++;
    }
    int count = 0;
    printf("\n");
    for (i = 0; i < n; i++)
    {
        printf("字符:%-5c频数:%-6ld哈夫曼编码:%-20s", TreeNode[i].letter, TreeNode[i].count, TreeNode[i].bits);
        count++;
        if (count % 4 == 0)
            printf("\n");
    }
    fseek(outputfilep, 0, SEEK_SET);//偏移量为零，重新找到开头位置写入参数
    //使用outputfilep指针来确定文件的开头，将编码信息写入存储文件
    fwrite(&filelength, 4, 1, outputfilep);
    //1是写入的元素大小，sizeof(filelength)是需要写入的长度。这些内容写入ouputfilep
    fwrite(&pt, 4, 1, outputfilep);//写入四字节
    fseek(outputfilep, pt, SEEK_SET);//从给定的开头位置，pt大小（压缩之后的位置），在outputfile里读取
    fwrite(&n, 4, 1, outputfilep);//写一个四字节的数据，要注意pt的累加
    //以上是写入压缩的各类参数：字符种类总数，字符总数
    for (i = 0; i < n; i++)
    {
        tmp = TreeNode[i];
        fwrite(&(TreeNode[i].letter), 1, 1, outputfilep);//写入各类字符
        pt++;
        c = strlen(TreeNode[i].bits);//映射
        fwrite(&c, 1, 1, outputfilep);
        pt++;
        j = strlen(TreeNode[i].bits);
        if (j % 8 != 0)//当位数不满8时，对该数进行补零操作（为了一字节八位数的保存）
        {
            for (f = j % 8; f < 8; f++)
                strcat(TreeNode[i].bits, "0");
        }
        while (TreeNode[i].bits[0] != 0)
        {
            c = 0;//赋值00000000
            for (j = 0; j < 8; j++)
            {
                if (TreeNode[i].bits[j] == '1')
                {
                    c = c << 1;
                    c += 1;//进行位运算
                }
                else 
                    c = c << 1;//不是1就补0
            }
            strcpy(TreeNode[i].bits, TreeNode[i].bits + 8);
            fwrite(&c, 1, 1, outputfilep);//将所得的编码信息写入文件
            pt++;
        }//以上写入各字符种类哈夫曼编码的数据

        TreeNode[i] = tmp;
    }
    fclose(inputfilep);
    fclose(outputfilep);//关闭文件

    printf("\n压缩后文件为:%s\n", outputfile);
    printf("压缩后文件有:%d字符\n", pt + 4);//之前写入了一个四字节的n，所以需要加4

    return pt + 4;//返回压缩成功信息
}
//函数：uncompress()，作用：解压缩文件，并将解压后的内容写入新文件
int uncompress(const char* filename, const char* outputfile)
{
    char temparray[256], bx[256];
    unsigned char c;
    long i, j, m, n, k, f, p, l;
    long filelength;
    int len = 0;
    FILE* inputfilep, * outputfilep;
    char cname[512] = { 0 };
    inputfilep = fopen(filename, "rb");//打开压缩的文件
    if (inputfilep == NULL)
    {
        printf("文件打开失败！");
        return 0;//若打开失败，则输出错误信息
    }

    outputfilep = fopen(outputfile, "wb");//打开新文件，用于解压缩
    if (outputfilep == NULL)
    {

        return 0;
    }
    fseek(inputfilep, 0, SEEK_END);//定下指针位置
    len = ftell(inputfilep);//记录的是inputfilep的位置，也就是字节数
    
    fseek(inputfilep, 0, SEEK_SET);
    printf("将要读取解压的文件:%s\n", filename);
    printf("当前文件有:%d字符\n", len);
    fread(&filelength, sizeof(long), 1, inputfilep);//读取原文件长
    fread(&f, sizeof(long), 1, inputfilep);//f读取的是需要压缩文件的长度，不包括参数
    fseek(inputfilep, f, SEEK_SET);//从文件的f位置后开始读
    fread(&n, sizeof(long), 1, inputfilep);//读取字符种类总数
    for (i = 0; i < n; i++)//读取压缩文件内容并转换成二进制码
    {
        fread(&TreeNode[i].letter, 1, 1, inputfilep);//读取字符(ASCII码)
        fread(&c, 1, 1, inputfilep);//读取字符编码的数据strlen(TreeNode[i].bits))
        p = (long)c;//读的是编码的长度，编码的长度与count反相关
        TreeNode[i].count = p;
        TreeNode[i].bits[0] = 0;//赋初值
        if (p % 8 > 0) //判断字节
            m = p / 8 + 1;
        else 
            m = p / 8;
        for (j = 0; j < m; j++)
        {
            fread(&c, 1, 1, inputfilep);//此步读取的是单个字符类的二进制码
            f = c;
            _itoa(f, temparray, 2);//将f的值转为2进制存入temparray，此时temparray存的是二进制
            f = strlen(temparray);
            for (l = 8; l > f; l--)
            {
                strcat(TreeNode[i].bits, "0");//位数不足，执行补零操作
            }
            strcat(TreeNode[i].bits, temparray);
        }
        TreeNode[i].bits[p] = 0;
    }//接受解压所需要的参数，读取必要的文件

    for (i = 0; i < n; i++)//每次都向后完成一次
    {
        k = i;//k用来记录变化的位置
        for (j = i + 1; j < n; j++)//每次都在没有排序的组里进行循环
            if (strlen(TreeNode[k].bits) > strlen(TreeNode[j].bits))//长度最小的是出现次数最多的
                k = j;//取短的
        tmp = TreeNode[i];//用t来暂时存储数据
        TreeNode[i] = TreeNode[k];//k是略长的值，换到后面
        TreeNode[k] = tmp;//实现交换
    }//排序的目的是快速找到读取解压对应的字母

    p = strlen(TreeNode[n - 1].bits);//p取了一个长度最长的
    fseek(inputfilep, 8, SEEK_SET);//前八位存了两个四字节数据
    m = 0;
    bx[0] = 0;
    while (1)
    {
        while (strlen(bx) < (unsigned int)p)
        {
            fread(&c, 1, 1, inputfilep);//读取压缩的文件
            f = c;//f是一个数，转换十进制再转换二进制丢失了0
            _itoa(f, temparray, 2);//f转换成二进制暂存入temparray
            f = strlen(temparray);
            for (l = 8; l > f; l--)
            {
                strcat(bx, "0");
            }//如果不足八位，补零
            strcat(bx, temparray);
        }
        for (i = 0; i < n; i++)
        {
            if (memcmp(TreeNode[i].bits, bx, TreeNode[i].count) == 0)
                break;//比较字节数是为了保证数组保存的二进制数字相同
        }
        strcpy(bx, bx + TreeNode[i].count);//进行赋值覆盖
        c = TreeNode[i].letter;//写入ASCII码
        fwrite(&c, 1, 1, outputfilep);//写入解码的文件
        m++;
        if (m == filelength)//限定写入只写到原文件的内容，再往后就是存储的参数
            break;
    }//读取压缩文件内容，解压缩的过程
    fclose(inputfilep);
    fclose(outputfilep);
    printf("解压后文件为:%s\n", outputfile);
    printf("解压后文件有:%d字符\n", filelength);
    return filelength;//输出成功信息
}





